// Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
// ---------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
// This library is distributed in the hope that it will be useful,
// but without any warranty; without even the implied warranty of merchantability
// or fitness for a particular purpose.
// You should have received a copy of the GNU Lesser General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
// ---------------------------------------------------------------------------
package pl.bristleback.server.bristle.config;

import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.exceptions.BristleRuntimeException;
import pl.bristleback.server.bristle.exceptions.ImplementationResolvingException;
import pl.bristleback.server.bristle.integration.spring.BristleSpringIntegration;
import pl.bristleback.server.bristle.integration.spring.SpringIntegrationUtil;
import pl.bristleback.server.bristle.utils.ResolverUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * This class provides functions which can be used in user implementations of resolvers of various configuration elements.
 * <p/>
 * Created on: 2010-10-14 21:11:19 <br/>
 *
 * @author Wojciech Niemiec
 */
public final class ClassConfigurationUtil {
  private static Logger log = Logger.getLogger(ClassConfigurationUtil.class.getName());

  private ClassConfigurationUtil() {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets list of class elements taken from plugin settings with prefix given in constructor,
   * each bound in {@link pl.bristleback.server.bristle.config.ClassConfiguration} class.
   * Special convention of names is used here, user should provide only name of setting property.
   * For example, to get message sender classes, name of property prefix is: 'messageSender'.
   *
   * @param settingPrefix  prefix first part of setting property.
   * @param pluginSettings map of settings from plugin configuration.
   * @return list of class configuration.
   */
  public static List<ClassConfiguration> getClassConfigurationsFromSettingsMap(String settingPrefix, Map<String, Object> pluginSettings) {
    List<Object> classConfigurationValues = getClassConfigurationValues(settingPrefix, pluginSettings);
    return processClassConfigurationValues(classConfigurationValues);
  }

  private static List<ClassConfiguration> processClassConfigurationValues(List<Object> classConfigurationValues) {
    List<ClassConfiguration> classConfigurations = new ArrayList<ClassConfiguration>();
    for (Object configurationValueObject : classConfigurationValues) {
      if (configurationValueObject instanceof String) {
        String configurationValue = (String) configurationValueObject;
        String[] configurationParts = configurationValue.split(":");
        if (configurationParts.length != 2) {
          throw new BristleRuntimeException("Exception while processing class configuration from plugin settings.\n"
            + "Bad format of plugin setting value: " + configurationValue + ", expected: name:type");
        }
        String configurationClassName = configurationParts[0].trim();
        String configurationClassValue = configurationParts[1].trim();
        ClassConfiguration classConfiguration = new ClassConfiguration(configurationClassName, configurationClassValue);
        classConfigurations.add(classConfiguration);
      } else {
        throw new BristleRuntimeException("Exception while processing class configuration from plugin settings.\n"
          + "Only String settings supported so far");
      }
    }
    return classConfigurations;
  }

  private static List<Object> getClassConfigurationValues(String settingPrefix, Map<String, Object> pluginSettings) {
    List<Object> configurationValues = new ArrayList<Object>();
    for (Map.Entry<String, Object> settingsEntry : pluginSettings.entrySet()) {
      if (isSettingCompatible(settingPrefix, settingsEntry.getKey())) {
        configurationValues.add(settingsEntry.getValue());
      }
    }
    return configurationValues;
  }

  private static boolean isSettingCompatible(String settingPrefix, String settingKey) {
    if (settingKey.startsWith(settingPrefix)) {
      if (!settingKey.matches(".+\\[\\d+\\]")) {
        throw new BristleRuntimeException("Exception while processing class configuration from plugin settings.\n"
          + "Bad format of plugin setting key: " + settingKey + ", expected format: " + settingPrefix + "[number]");
      }
      return true;
    }
    return false;
  }

  public static <T> T getInstanceFromConfiguration(Class<T> type, ClassConfiguration configuration) {
    if (BristleSpringIntegration.isIntegrationEnabled()) {
      if (SpringIntegrationUtil.containsBean(configuration.getType())) {
        return SpringIntegrationUtil.getSpringBean(configuration.getType(), type);
      } else {
        return retrieveInstance(type, configuration);
      }
    } else {
      return retrieveInstance(type, configuration);
    }
  }

  /**
   * Returns instance of class from given class configuration. Method checks the way in which class should be retrieved.
   * Depending on configuration, it creates new instance of class or gets instance from Spring context.
   *
   * @param type          type of class.
   * @param configuration information about class.
   * @param <T>           type of instance.
   * @return instance of class taken from configuration.
   */
  @SuppressWarnings("unchecked")
  private static <T> T retrieveInstance(Class<T> type, ClassConfiguration configuration) {
    try {
      ResolverUtil resolverUtil = new ResolverUtil();
      Class<T> clazz = resolverUtil.getImplementation(type, configuration.getType());
      return clazz.newInstance();
    } catch (Exception e) {
      throw new ImplementationResolvingException(configuration.getType(), type);
    }
  }
}
